#define _LCDMOD_
#define LCDMOD_VERSION "2.5.2"
#include <generated/autoconf.h>
#include "config.h"
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/cdev.h>
#include <linux/poll.h>
#include <linux/bitops.h>
#include <linux/sched.h>
#include <asm/uaccess.h>
#include <linux/parport.h>
#include <linux/parport_pc.h>
#include <linux/miscdevice.h>
#include "lcdmod-lib.h"
#include "hd44780-inst-def.h"
#include "hd44780-operations.h"
#include "cgram/charmap.h"

MODULE_LICENSE("GPL");
MODULE_DESCRIPTION( "LCD parallel port driver for HD44780 compatible controllers switched by a counter with enable line." );
MODULE_VERSION(LCDMOD_VERSION);
/* used as port base and after port_init stores the value of counter */
int base = DFLT_BASE;
module_param(base, int, 0444);
MODULE_PARM_DESC(base, "Parallel port i/o base address default: DFLT_BASE.");

static ulong checktime = 60;
module_param(checktime, ulong, 0444);
MODULE_PARM_DESC(checktime, "time in sec after checking timer expires. default:60");

struct lcdmod_dev lcddev; /* the very big struct in lcdmod-lib.h */

/* cursor position - in the upper left edge is position (x,y) = (0,0) */
unsigned int row, col;  /* virtual cursor position*/

/* two dimensional display state variable */
unsigned char state[DISP_ROWS][DISP_COLS] = { [0 ... DISP_ROWS-1][ 0 ... DISP_COLS-1] = ' ' };
DEFINE_MUTEX(state_mutex); /* mutex for state variable */

static struct delayed_work timer_work; /* timer delayed worker */
static struct fasync_struct *asyncqueue; /* asynchronius events signal notifing */
wait_queue_head_t irq_wait; /* irq waiting queue */


#include "hd44780_high_operations.c"

#include "lcdmod-irq.c"

#include "lcdmod-cdev.c"

#include "lcdmod-proc.c"

#include "lcdmod_timer.c"

static void lcdmod_dev_init(struct lcdmod_dev *lcddev) 
{
	lcddev->pd = NULL;
	/* initialization of delayed work is in lcdmod.c */
	lcddev->reset_num = 0;
	lcddev->next_num = 0;
	lcddev->read_num = 0;
	lcddev->write_num = 0;
	lcddev->bfchecks_num = 0;
	lcddev->flags = 0;
	lcddev->irq_buff = 0;
	lcddev->status = 0;
}

static void lcdmod_lcd_init(struct lcdmod_lcd *lcd) 
{
	memset(lcd->cgram, 0, sizeof(char) * 64);
	lcd->cur_row = 0;
	lcd->cur_col = 0;
	lcd->errors_num = 0;
}

/* register port with given base and fills lcddev[x].pd struct */
static int register_port(struct lcdmod_dev *lcddev, int base)
{
	struct parport *pp;
	pp = parport_find_base( base );
	if (!pp) {
		PERR( "no parport found at %#x.\n", base );
		return -ENODEV;
	}
	lcddev->pd = parport_register_device( pp, "lcdmod", NULL, NULL, 
					lcdmod_irq, PARPORT_MODE_TRISTATE, lcddev );
	if (!lcddev->pd) {
		PERR("can't register device.\n");
		goto err_register_device;
	}
	if (parport_claim(lcddev->pd)) {
		PERR( "can't claim port.\n");
		goto err_claim;
	}
	return 0; /* succes */
	
err_claim:
	parport_unregister_device(lcddev->pd);
err_register_device:
	return -EBUSY;
}

static struct miscdevice lcdmod_miscdevice = {
	.minor = MISC_DYNAMIC_MINOR,
	.name = DEV_FILE_NAME,
	.fops = &lcdmod_fops
};

/****	MODULE DECLARATIONS *****/
static int __init start_module(void)
{
	int ret;
	
	PDEBUG("info %03x %d %d %d %d\n", base, DISP_ROWS, DISP_COLS, ROWS_PER_CTRL, NUM_CONTROLLERS);
	
	/* register ports, initialize struct lcddev */
	lcdmod_dev_init(&lcddev);
	for ( ret = 0; ret < NUM_CONTROLLERS; ret++ )
		lcdmod_lcd_init(&lcddev.lcd[ret]);
	
	/* initialize waitqueue, need to do it before registering port */
	init_waitqueue_head(&irq_wait);
	INIT_DELAYED_WORK(&lcddev.work, irq_worker);
	
	ret = register_port(&lcddev, base);
	if (ret) {
		PERR("Cannot register port at %#x.\n", base);
		goto fail_port_register;
	}
	PINFO( "port %#x registered.\n", base);
	
	/* initialize all controllers and write cgram */
	init_displays();
	
	/* create proc read entries */
	ret = -ENOMEM;
	if (lcdmod_proc_init())
		goto fail_lcdmod_proc_init;
	
	/* register misc device */
	ret = misc_register(&lcdmod_miscdevice);
	if (ret) {
		PERR( "Unable to register \"Hello, world!\" misc device\n" );
		goto fail_register_chrdev;
	}
	
	INIT_DELAYED_WORK(&timer_work, timer_worker);
	schedule_delayed_work(&timer_work, HZ );
	
	PINFO( "init OK, irq on, controlers %d, rows: %d, columns: %d\n", NUM_CONTROLLERS, DISP_ROWS, DISP_COLS );
	return 0; /* succes */

fail_register_chrdev:
fail_lcdmod_proc_init:
	parport_release( lcddev.pd );
	parport_unregister_device( lcddev.pd );
fail_port_register:
	return ret;
}

static void __exit exit_module(void)
{
	PINFO("Module shutdown \n");
	
	misc_deregister(&lcdmod_miscdevice);
	lcdmod_proc_exit();
	/*writeInst_all( 0x01 ); *//* clear displays */
	
	cancel_delayed_work_sync(&timer_work);
	flush_scheduled_work();
	
	parport_release(lcddev.pd);
	parport_unregister_device(lcddev.pd);
	
	return;
}

module_init(start_module);
module_exit(exit_module);
